import datetime
import json
import functools
import random
import traceback
from copy import deepcopy

from werkzeug.security import check_password_hash, generate_password_hash

from flask_login import login_user, logout_user, current_user

from app.chess import login_manager
from app.chess import pika_pool
from app import tools
from app import dbop
from app import error
# from app import tasks
from app.rule import ChessGame
from app.val_def import *


@login_manager.user_loader
def load_user(user_id):
    rv = dbop.UserDAO.get_record(user_id)
    return rv


class RequireRole(object):

    def __init__(self, role=None):
        self.role = role

    def __call__(self, method):
        @functools.wraps(method)
        def wrapper(*args, **kwargs):
            if not current_user.is_authenticated:
                return {
                    'error': error.E_NOT_LOGIN
                }
            elif self.role:  # and current_user.type not in self.role:
                pass
                if isinstance(self.role, list):  # and current_user.type not in self.role:
                    if current_user.role not in self.role:
                        return {
                            'error': error.E_USER_TYPE_WRONG
                        }
                elif current_user.role != self.role:
                    print('user.type:', self.role)    #, current_user.type
                    return {
                        # 'error': err_map.get(self.role)
                        'error': error.E_USER_TYPE_WRONG
                    }

            try:
                rv = method(*args, **kwargs)
            except Exception as e:
                # print e.message
                print(traceback.format_exc())
                dbop.session_rollback()
                return {
                    'error': error.E_PROC_WRONG
                }

            return rv

        return wrapper


require_login = RequireRole()


def api_create_game():
    # param = tools.proc_request_data()
    token = "testmatch123sdfeadde_red"
    dbop.ChessMatchDAO.update_record(None, {
        "red_player": 1,
        "black_player": -1,
        "token": token,
        "state": "Ongoing",
        "create_time": datetime.datetime.now()
    })

    return {
        "error": 0
    }


def api_chess_game_process():
    params = tools.proc_request_data()
    match_id = params.get('match_id')
    records = dbop.ChessGameProcessDAO.get_match_record(match_id)
    if not records:
        params["seq"] = 1
        game = ChessGame()
    else:
        params["seq"] = records[-1].seq + 1
        last_record = records[-1]
        status_json = tools.safe_json_loads(last_record.status)
        game = ChessGame(status_json)

    # game = load_chess_game_records(records)
    # if 'Error.ProcWrong' == game:
    #     return {
    #         'error': error.E_PROC_WRONG
    #     }

    move = params.get('move')
    move = (move[0], tuple(move[1]), tuple(move[2]))
    # 只初步检查了基本的走棋规则， 但是没有检查将军的情况， 也没有实际改变状态。
    res = game.step_check(move)
    if 'OK' != res:
        return {
            "error": error.E_PARAM_WRONG,
            'err_msg': res
        }
    # 检查对将的情况和将军的情况。
    target_piece = game.change_status_raw(move)
    # 下面这个函数是否放在 change_status_raw 里面比较好。
    game.build_piece_index()
    w_same_road = game.check_w_same_road()
    print("check_w_same_road:", w_same_road)
    # 对将， 需要复原。
    if w_same_road:
        game.rewind_status_raw(move, target_piece)
        return {
            "error": error.E_FORBID_MOVE,
            "err_msg": "Error.SameRoadW"
        }

    # 检查是否有将军的情况
    result = game.get_piece_attack()
    print('attack info:', result)
    attack_info_backward = result.get('attack_info_backward').get(game.cur_player)

    # w_attacked = game.check_w_attacked(game.cur_player)
    w_attacked = (game.cur_player + 'W') in attack_info_backward
    print("w_attacked:", w_attacked)
    if w_attacked:
        game.rewind_status_raw(move, target_piece)
        return {
            "error": error.E_FORBID_MOVE,
            "err_msg": "Error.AttackedW"
        }
    # 可以最终确定走子。
    game.switch_player()

    data = game.get_bord_status()

    # 已经切换了执棋方
    attack_info_backward = result.get('attack_info_backward').get(game.cur_player)
    w_attacked = (game.cur_player + 'W') in attack_info_backward
    # 只有将军了才去检查是否杀死了比赛
    if w_attacked:
        # 检查有没有杀死比赛
        kill_game = game.check_kill_game()
        # game.print_status()

        data['attack_w'] = True
        data['kill_game'] = kill_game
    else:
        data['attack_w'] = False
        data['kill_game'] = False
    data['attack_info_backward'] = result.get('attack_info_backward')

    print('data:', data)

    # 保存记录
    params['move'] = json.dumps(params.get('move'))
    params['dead_piece'] = target_piece
    params['status'] = json.dumps(data)
    params['update_time'] = datetime.datetime.now()
    dbop.ChessGameProcessDAO.update_record(None, params)

    # if 'OK' != res:
    #     return {
    #         'error': error.E_PARAM_WRONG,
    #         'err_msg': res
    #     }
    # #
    # params['move'] = json.dumps(params.get('move'))
    # params['update_time'] = datetime.datetime.now()
    # dbop.ChessGameProcessDAO.update_record(None, params)
    # game.build_piece_attack('R')
    # game.build_piece_attack('B')
    # data = game.get_bord_status()
    # # 判断是否将军， 如果将军， 对方是否有解法， 如果没有解法，提示绝杀。
    # cur_player = data.get('player')
    # attack_reverse = data.get('player_attack_reverse')[cur_player]
    # piece_w = cur_player + 'W'
    # print('attack_reverse:', piece_w, attack_reverse, data)
    # if piece_w in attack_reverse:
    #     # 对方被将军， 判断是否是死将
    #     copy_game = deepcopy(game)
    #     if copy_game.check_dead_game():
    #         # print("dead game")
    #         data['dead_game'] = True
    #         game.dead_game = True
    # 机器人走棋的代码， 现在不用。
    # match_record = dbop.ChessMatchDAO.get_record(match_id)
    # if match_record.red_player < 0 or match_record.black_player < 0:
    #     from app import tasks
    #     # 通知机器人走棋
    #     tasks.match_player.delay()

    return {
        "error": 0,
        "data": data
    }


def api_wait_opponent_move():
    params = tools.proc_request_data()
    match_id = params.get('match_id')
    seq = params.get('seq')
    player = params.get('player')
    if not match_id or seq is None or not player:
        return {
            "error": error.E_PARAM_WRONG
        }

    record = dbop.ChessGameProcessDAO.get_record_by_filter_key({
        'match_id': match_id,
        'seq': seq
    })
    if seq and not record:
        return {
            "error": error.E_PROC_WRONG
        }

    end_dt = datetime.datetime.now() + datetime.timedelta(seconds=120)
    while True:
        dt = datetime.datetime.now()
        if dt > end_dt:
            return {
                "error": error.E_PROC_TIMEOUT
            }

        move_record = dbop.ChessGameProcessDAO.get_record_by_filter_key({
            'match_id': match_id,
            'seq': seq + 1
        })
        # print('move_record:', move_record, dt, seq + 1)
        if move_record:
            # all_records = dbop.ChessGameProcessDAO.get_match_record(match_id)
            # game = load_chess_game_records(all_records)
            # if 'Error.ProcWrong' == game:
            #     return {
            #         "error": error.E_PROC_WRONG
            #     }
            #
            # game.build_piece_attack('R')
            # game.build_piece_attack('B')
            data = tools.safe_json_loads(move_record.status)

            return {
                "error": 0,
                "move": tools.safe_json_loads(move_record.move),
                "data": data
            }
        tools.app_sleep(0.5)


def api_wait_pika_move():
    params = tools.proc_request_data()
    match_id = params.get("match_id")
    records = dbop.ChessGameProcessDAO.get_match_record(match_id)
    if not records:
        params["seq"] = 1
        game = ChessGame()
    else:
        params["seq"] = records[-1].seq + 1
        last_record = records[-1]
        status_json = tools.safe_json_loads(last_record.status)
        game = ChessGame(status_json)
    uci_pos = game.get_uci_pos()
    print("uci_pos:", uci_pos, game.cur_player)
    # best move b9c7 翻译成 本项目的表示法，翻译成黑方， 马二进三
    uci_cmd = "position fen %s %s - - 0 1\n" % (uci_pos, {"B": "b", "R": "w"}.get(game.cur_player))
    print("uci_cmd:", uci_cmd)

    """
    
info depth 17 seldepth 33 multipv 1 score cp -36 nodes 612252 nps 771098 hashfull 230 tbhits 0 time 794 pv h9g7 g3g4 i9h9 c3c4 h7i7 h0g2 b7e7 b0c2 b9c7 a0b0 a9a8 b2a2 a8d8 i0i2 h9h5 i2h2 h5h2 e2h2 d8d5 b0b6 g6g5 b6c6
info depth 18 seldepth 30 multipv 1 score cp -39 nodes 863799 nps 777496 hashfull 327 tbhits 0 time 1111 pv h9g7 g3g4 c6c5 h0g2 b9c7 i0h0 i9h9 b2b6 c7b5 b0a2 a6a5 a0a1 g9e7 a1d1 f9e8
bestmove h9g7 ponder g3g4

    """
    engine, lock = pika_pool.get_engine()
    try:
        engine.stdin.write(uci_cmd)
        engine.stdin.flush()
        engine.stdin.write("go depth 18\n")
        engine.stdin.flush()
        output = pika_pool.get_engine_output(engine)
        # output = engine.stdout.readlines()
        # output, err = engine.communicate()
        # print(output)
        print("go depth 18:")
        # for item in output:
        #     print(item)
        last_line_part = [x.strip() for x in output[-1].split(" ")]
        if last_line_part[0] == "bestmove":
            uci_coord = last_line_part[1]
            move = game.get_move_from_uci_coord(game.cur_player, uci_coord)
            #
            check_res = game.step_check(move)
            print("check_res:", check_res)
            target_piece = game.change_status_raw(move)
            # 检查对将和被将的情况。 然后 切换状态。记录到数据库里。
            result = game.get_piece_attack()
            print('attack info:', result)

            game.switch_player()
            data = game.get_bord_status()

            # 已经切换了执棋方
            attack_info_backward = result.get('attack_info_backward').get(game.cur_player)
            w_attacked = (game.cur_player + 'W') in attack_info_backward
            # 只有将军了才去检查是否杀死了比赛
            if w_attacked:
                # 检查有没有杀死比赛
                kill_game = game.check_kill_game()
                # game.print_status()

                data['attack_w'] = True
                data['kill_game'] = kill_game
            else:
                data['attack_w'] = False
                data['kill_game'] = False
            data['attack_info_backward'] = result.get('attack_info_backward')

            print('data:', data)

            # 保存记录
            params['move'] = json.dumps(params.get('move'))
            params['dead_piece'] = target_piece
            params['status'] = json.dumps(data)
            params['update_time'] = datetime.datetime.now()
            dbop.ChessGameProcessDAO.update_record(None, params)

            return {
                "error": 0,
                "move": move
            }
    except Exception as e:
        print(e)
    finally:
        pika_pool.release_engine(engine, lock)

    return {
        "error": 0
    }


def api_get_pika_move():
    params = tools.proc_request_data()
    uci_cmd = params.get("uci_cmd")
    depth = params.get("depth")
    # best move b9c7 翻译成 本项目的表示法，翻译成黑方， 马二进三
    # uci_cmd = "position fen %s %s - - 0 1\n" % (uci_pos, {"B": "b", "R": "w"}.get(game.cur_player))
    print("uci_cmd:", uci_cmd, depth)

    engine, lock = pika_pool.get_engine()
    try:
        engine.stdin.write(uci_cmd+"\n")
        engine.stdin.flush()
        engine.stdin.write("go depth %s\n" % depth)
        engine.stdin.flush()
        output = pika_pool.get_engine_output(engine)
        # output = engine.stdout.readlines()
        # output, err = engine.communicate()
        # print(output)
        print("go depth 18:")
        for item in output:
            print(item)
        last_line_part = [x.strip() for x in output[-1].split(" ")]

        uci_coord = None
        for i in range(5):
            if last_line_part[0] == "bestmove":
                uci_coord = last_line_part[1]
                break
            output = pika_pool.get_engine_output(engine)
            for item in output:
                print(item)
            last_line_part = [x.strip() for x in output[-1].split(" ")]

        if uci_coord:
            return {
                "error": 0,
                "move": uci_coord
            }
    except Exception as e:
        print(e)
    finally:
        pika_pool.release_engine(engine, lock)

    return {
        "error": 0
    }


def api_load_chess_game():
    params = tools.proc_request_data()
    match_id = params.get('match_id')

    if not match_id:
        return {
            "error": error.E_PARAM_WRONG
        }

    records = dbop.ChessGameProcessDAO.get_match_last_record(match_id)
    print("match last records:", records)
    if not records:
        game = ChessGame()
        data = game.get_bord_status()
    else:
        status_json = tools.safe_json_loads(records.status)
        data = status_json

    from app import tasks
    tasks.match_player()

    # if not records:
    #     return {
    #         'error': error.E_NO_RECORDS
    #     }
    #
    # print("start load game ...")
    # game = load_chess_game_records(records)
    # if 'Error.ProcWrong' == game:
    #     return {
    #         'error': error.E_PROC_WRONG
    #     }
    # print("finish load game ...")

    # print("start build...")
    # game.build_piece_attack('R')
    # game.build_piece_attack('B')
    # print("finish build...")
    # data = game.get_bord_status()

    return {
        'error': 0,
        'data': data
    }


def get_match_history():
    params = tools.proc_request_data()
    match_id = params.get('match_id')

    if not match_id:
        return {
            "error": error.E_PARAM_WRONG
        }
    records = dbop.ChessGameProcessDAO.get_match_record(match_id)
    print('records:', len(records))
    data = [{
        'seq': x.seq,
        'move': x.move,
        'dead_piece': x.dead_piece,
        'status': tools.safe_json_loads(x.status),
        # 'move': x.move,
    } for x in records]

    return {
        "error": 0,
        "data": data
    }


def cal_machine_move():
    params = tools.proc_request_data()
    match_id = params.get('match_id')
    seq = params.get('seq')
    #
    record = dbop.ChessGameProcessDAO.get_record_by_filter_key({
        'match_id': match_id,
        'seq': seq
    })
    print('record:', record)
    data_json = tools.safe_json_loads(record.status)
    kill_game = data_json.get('kill_game')
    if kill_game:
        return {
            "error": error.E_PARAM_WRONG
        }
    game = ChessGame(data_json)
    from app.machine import machine_move_select
    res = machine_move_select(game)
    print("res:", res)

    return {
        "error": 0,
        "data": res
    }


def api_reset_chess_game():
    params = tools.proc_request_data()
    match_id = params.get('match_id')

    if not match_id:
        return {
            "error": error.E_PARAM_WRONG
        }

    dbop.ChessGameProcessDAO.delete_match_record(match_id)

    game = ChessGame()
    # game.build_piece_attack('R')
    # game.build_piece_attack('B')
    data = game.get_bord_status()
    return {
        'error': 0,
        'data': data
    }


def api_rewind_chess_game():
    params = tools.proc_request_data()
    match_id = params.get('match_id')
    # step = params.get('step')

    if not match_id:
        return {
            "error": error.E_PARAM_WRONG
        }

    records = dbop.ChessGameProcessDAO.get_match_record(match_id)
    if not records:
        return {
            'error': error.E_NO_RECORDS,
            'err_msg': 'error.E_NO_RECORDS'
        }
    dbop.ChessGameProcessDAO.delete_match_record_some(match_id, records[-1:])
    # 下面这个函数删掉了， 这个接口也要重写。
    game = load_chess_game_records(records[:-1])
    if 'Error.ProcWrong' == game:
        return {
            'error': error.E_PROC_WRONG
        }
    # game.build_piece_attack('R')
    # game.build_piece_attack('B')
    data = game.get_bord_status()
    return {
        'error': 0,
        'data': data
    }


def api_user_register():
    params = tools.proc_request_data()
    username = params.get('username')
    password = params.get('password')
    if not username or not password:
        return {
            'error': error.E_PARAM_WRONG
        }
    user_obj = dbop.UserDAO.get_record_by_username(username)
    if user_obj:
        return {
            'error': error.E_USER_EXIST
        }
    dbop.UserDAO.update_record(None, {
        'username': username,
        'password': generate_password_hash(password),
        'create_time': datetime.datetime.now()
    })

    return {
        "error": 0
    }


def api_user_login():
    params = tools.proc_request_data()
    username = params.get('username')
    password = params.get('password')
    if not username or not password:
        return {
            'error': error.E_PARAM_WRONG
        }
    user_obj = dbop.UserDAO.get_record_by_username(username)
    if not user_obj:
        return {
            'error': error.E_USER_NOT_EXIST
        }
    if not check_password_hash(user_obj.password, password):
        return {
            'error': error.E_PASSWORD_WRONG
        }

    login_user(user_obj)

    return {
        "error": 0
    }


def api_user_logout():
    params = tools.proc_request_data()
    return {
        "error": 0
    }


def api_test():
    params = tools.proc_request_data()

    return {
        "error": 0
    }


def api_game_enter_district():
    from app import tasks

    params = tools.proc_request_data()

    username = current_user.username
    record = dbop.GameDistrictDAO.get_record_by_username(username)
    if not record:
        # tasks
        # return {
        #     'error': error.E_STATE_WRONG
        # }
        # 计算一个随机的玩家， 红方还是黑方，
        # 0表示红方， 1表示黑方。 红方去寻找黑方的玩家。
        player = random.randint(0, 1)
        print('player:', player)

        #
        dbop.GameDistrictDAO.update_record(None, {
            'uid': current_user.id,
            'username': current_user.username,
            'state': 'Pending',
            'player': player,
            'param': tools.safe_json_dump(params),
            'update_time': datetime.datetime.now()
        })
    else:
        player = record.player

    # Todo:  在异步任务处理里面进行玩家匹配
    # 只能红方玩家去找黑方玩家，
    # 1. 会不会两个红方玩家都去找一个黑方玩家。 有可能，所以是不是还是需要在异步队列里面处理。
    # 为了避免递归导入， 只能放在这里导入。
    tasks.match_player.delay()
    for _ in range(3):
        player_record = dbop.GameDistrictDAO.get_record_by_username(username)
        print("player_record:", player_record.state)
        if player_record and 'OnChess' == player_record.state:
            # if PLAYER_RED == player:
            #     filter_key = {
            #         'red_player': player_record.uid
            #     }
            # else:
            #     filter_key = {
            #         'black_player': player_record.uid
            #     }
            # filter_key = {
            #     'state': 'Ongoing',
            #     'match_id': player_record.match_id
            # }
            match_record = dbop.ChessMatchDAO.get_record(player_record.match_id)
            print('match_record:', player_record.match_id, match_record)
            if not match_record:
                return {
                    "error": error.E_PROC_WRONG
                }
            # chess_records = dbop.ChessGameProcessDAO.get_match_record(match_record.id)
            # game = load_chess_game_records(chess_records)
            # data = game.get_bord_status()

            return {
                "error": 0,
                "match_id": match_record.id,
                'player': player,
                # "data": data
            }
        else:
            tools.app_sleep(1)

    return {
        "error": error.E_PROC_TIMEOUT
    }


def api_game_exit_district():
    params = tools.proc_request_data()
    match_id = params.get('match_id')
    player = params.get('player')
    if not match_id or not player:
        return {
            "error": error.E_PARAM_WRONG
        }
    match_record = dbop.ChessMatchDAO.get_record(match_id)
    if not match_record:
        return {
            "error": error.E_PARAM_WRONG
        }

    if ('B' == player and match_record.black_player != current_user.id) or \
       ('R' == player and match_record.red_player != current_user.id):
        return {
            "error": error.E_PARAM_WRONG
        }
    # 更新比赛记录的状态
    try:
        dbop.begin_transction()
        dbop.ChessMatchDAO.update_record(match_record.id, {
            "state": "Finish"
        })
        # 删除游戏区的记录
        # 大于0 表示是人类， 小于0表示是机器
        # red_player_record = dbop.GameDistrictDAO.get_record_by_filter_key({
        #     "uid": match_record.red_player
        # })
        # dbop.delete_record(red_player_record)
        # red_player_record = dbop.GameDistrictDAO.get_record_by_filter_key({
        #     "uid": match_record.red_player
        # })
        delete_records = [dbop.GameDistrictDAO.get_record_by_filter_key({"uid": uid})
                          for uid in [match_record.red_player, match_record.black_player]]
        delete_result = [dbop.delete_record(record) for record in delete_records]
        dbop.session_commit()

    except Exception as e:
        print(traceback.format_exc())
        dbop.session_rollback()
        return {
            "error": error.E_PROC_WRONG
        }

    return {
        "error": 0
    }

